package org.digitarts.rtms.spring.collectors.jdbc;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.sql.DataSource;

import org.digitarts.rtms.helpers.StringHelper;
import org.digitarts.rtms.spring.collectors.CollectorBase;

public class JDBCCollector extends CollectorBase
{
	protected DataSource							dataSource				= null;
	protected String								query					= null;
	protected int									bindCount				= 0;
	protected Map<Integer, IBindVariableProvider>	binds					= new HashMap<Integer, IBindVariableProvider>();
	protected Set<QueryMap>							queryMaps				= new HashSet<QueryMap>();
	protected Map<String, QueryMap>					scopedQueryMaps			= new HashMap<String, QueryMap>();
	protected Map<String, QueryMap>					processedQueryMaps		= new HashMap<String, QueryMap>();
	protected String[]								availabilityNameSpace	= null;
	protected String[]								availabilityState		= null;
	protected String[]								availabilityConnectTime	= null;

	public JDBCCollector()
	{
		super();
	}

	@Override
	public void initCallback()
	{
		if (availabilityNameSpace != null)
		{
			formatSegments(availabilityNameSpace);
			availabilityState = StringHelper.append(availabilityNameSpace, "Availability");
			availabilityConnectTime = StringHelper.append(availabilityNameSpace, "Connect Time (ms)");

		}
	}

	public void setBinds(Map<Integer, IBindVariableProvider> bindArgs)
	{
		bindCount = bindArgs.size();
		binds.putAll(bindArgs);
		log.info("Added Bind[" + bindCount + "]");
	}

	@Override
	public void collectCallback() throws Exception
	{
		Connection conn = null;
		PreparedStatement ps = null;
		ResultSet rset = null;
		ResultSetMetaData rsmd = null;
		try
		{
			long start = System.currentTimeMillis();
			try
			{
				conn = dataSource.getConnection();

			}
			catch (Exception e)
			{
				tracer.traceSticky(-1, availabilityConnectTime);
				tracer.traceSticky(-1, availabilityState);
				throw e; // we're only catching to log the availability.
			}
			if (availabilityNameSpace != null)
			{
				long connectTime = System.currentTimeMillis() - start;
				tracer.traceSticky(connectTime, availabilityConnectTime);
				tracer.traceSticky(1, availabilityState);
			}
			ps = conn.prepareStatement(query);
			for (int i = 1; i <= bindCount; i++)
			{
				binds.get(i).bind(ps, i);
			}

			rset = ps.executeQuery();
			rsmd = rset.getMetaData();
			int rowCount = 0;
			int colCount = rsmd.getColumnCount();

			List<String[]> rowSet = new ArrayList<String[]>();
			while (rset.next())
			{
				String[] row = new String[colCount];
				for (int x = 0; x < colCount; x++)
				{
					row[x] = rset.getString(x + 1);
				}
				rowSet.add(row);
				rowCount++;
			}
			rset.close();
			ps.close();

			// Process acquired data.
			String[] localNameSpace = null;
			String traceValue = null;
			String mName = null;
			String tracerType = null;

			processedQueryMaps.clear();
			for (String[] dataRow : rowSet)
			{
				try
				{
					for (QueryMap map : queryMaps)
					{
						// Get value, format segment, format metricName,
						// generate local namespace,
						// trace
						traceValue = dataRow[map.getValueColumn()];
						localNameSpace = formatSegments(map.getSegments(), tracingNameSpace, dataRow);
						mName = formatField(map.getMetricName(), dataRow);
						tracerType = formatField(map.getMetricType(), dataRow);
						localNameSpace = StringHelper.append(localNameSpace, mName);
						tracer.trace(traceValue, tracerType, localNameSpace);
						processedQueryMaps.put((String) StringHelper.flattenArray("##~##", (Object[]) localNameSpace), map);
						localNameSpace = null;
						traceValue = null;
					}
				}
				catch (Exception e)
				{
					if (logErrors)
					{
						log.error("Failed to process data row", e);
					}
				}
			}
			if (scopedQueryMaps.size() > 0)
			{
				for (Entry<String, QueryMap> entry : scopedQueryMaps.entrySet())
				{
					if (!processedQueryMaps.containsKey(entry.getKey()))
					{
						// these query maps were run the last collect, but not
						// this time
						// so we need to trace a default if one is defined.
						if (entry.getValue().getDefaultValue() != null)
						{
							tracer.trace(entry.getValue().getDefaultValue(), entry.getValue().getMetricType(), entry.getKey().split("##~##"));
							log.warn("Scope Reset on [" + entry.getKey() + "]");
						}
					}
				}
			}
			scopedQueryMaps.clear();
			scopedQueryMaps.putAll(processedQueryMaps);

			long elapsed = System.currentTimeMillis() - start;
			tracer.trace(elapsed, elapsedTimeMetricNameSpace);
			if (log.isDebugEnabled())
				log.debug("[" + objectName + "]\n\t Collection of " + rowCount + " Rows Complete in " + elapsed + " ms.");
		}
		catch (Throwable e)
		{
			if (logErrors)
			{
				log.error("Failed to collect [" + objectName + "]", e);
			}
		}
		finally
		{
			try
			{
				rset.close();
			}
			catch (Exception e)
			{
			}
			try
			{
				ps.close();
			}
			catch (Exception e)
			{
			}
			try
			{
				conn.close();
			}
			catch (Exception e)
			{
			}
		}
	}

	protected String[] formatSegments(final String[] fragments, final String[] prefix, final String[] data)
	{
		String newFragments[] = new String[fragments != null ? fragments.length : 0];
		for (int i = 0; i < newFragments.length; i++)
		{
			newFragments[i] = formatField(fragments[i].trim(), data);
		}
		return StringHelper.append(prefix, newFragments);
	}

	public String getQuery()
	{
		return query;
	}

	public void setQuery(String query)
	{
		this.query = query;
	}

	public void setDataSource(DataSource dataSource)
	{
		this.dataSource = dataSource;
	}

	public Set<QueryMap> getQueryMaps()
	{
		return queryMaps;
	}

	public void setQueryMaps(Set<QueryMap> queryMaps)
	{
		this.queryMaps = queryMaps;
	}

	public String[] getAvailabilityNameSpace()
	{
		return availabilityNameSpace;
	}

	public void setAvailabilityNameSpace(String[] availabilityNameSpace)
	{
		this.availabilityNameSpace = availabilityNameSpace;
	}

}
